#pragma once

#include "DebugLog.h"
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
#include <AsyncJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <LittleFS.h>

#include "ESP8266WSYBemfa.h"

extern ESP8266WSYBoot boot;
extern BemfaMqtt bemfaMqtt;
extern String hostname;
extern String password;
static String PARAM_TOPIC = "topic";
static String PARAM_MSG = "msg";

static const char *mimetype(const String &filename) {
    auto fn = filename;
    fn.toLowerCase();

    if (fn.endsWith(".htm") || fn.endsWith(".html")) {
        return "text/html";
    }
    if (fn.endsWith(".css")) {
        return "text/css";
    }
    if (fn.endsWith(".js")) {
        return "application/javascript";
    }
    if (fn.endsWith(".json")) {
        return "application/json";
    }
    if (fn.endsWith(".png")) {
        return "image/png";
    }
    if (fn.endsWith(".jpg") || fn.endsWith(".jpeg")) {
        return "image/jpeg";
    }
    if (fn.endsWith(".gif")) {
        return "image/gif";
    }

    return "text/plain";
}

class Httpd {
public:
    Httpd(uint16_t port)
        : _server(port){};
    typedef void (*MessageListener)(const String &topic, const String &msg);
    void onMessage(MessageListener listener) {
        _listener = listener;
    };

    void begin() {
        // Init FS
        LittleFS.begin();

        // Init web server

        // route - /version
        _server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request) {
            auto response = request->beginResponseStream("application/json");
            DynamicJsonDocument v(256);

            v["sdk"] = ESP.getSdkVersion();
            v["boot"] = ESP.getBootVersion();
            v["core"] = ESP.getCoreVersion();
            v["full"] = ESP.getFullVersion();

            serializeJsonPretty(v, *response);
            request->send(response);
        });

        // route - GET `/api/xxxxxx`
        _server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
            _apiStatusGet(request);
        });

        _server.on("/api/login", HTTP_POST, [this](AsyncWebServerRequest *request) {
            DynamicJsonDocument v(256);
            String token = request->getParam("token", true)->value();
            DEBUG_LOG("[HTTPD] token:");
            DEBUG_LOG_LN(token);
            if (token == password) {
                v["code"] = 0;
                v["msg"] = "ok";
            } else {
                v["code"] = -1;
                v["msg"] = "fail";
            }
            auto response = request->beginResponseStream("application/json");
            serializeJsonPretty(v, *response);
            request->send(response);
        });

        _server.on("/api/savewifi", HTTP_POST, [this](AsyncWebServerRequest *request) {
            DynamicJsonDocument v(256);
            String _wifi_ssid = request->getParam("ssid", true)->value();
            String _wifi_pwd = request->getParam("password", true)->value();
            DEBUG_LOG("[HTTPD] ssid=");
            DEBUG_LOG_LN(_wifi_ssid);
            DEBUG_LOG("[HTTPD] password=");
            DEBUG_LOG_LN(_wifi_pwd);
            boot.setWifi(_wifi_ssid, _wifi_pwd);
            v["code"] = 0;
            v["msg"] = "save";
            auto response = request->beginResponseStream("application/json");
            serializeJsonPretty(v, *response);
            request->send(response);
        });

        _server.on("/api/savesystem", HTTP_POST, [this](AsyncWebServerRequest *request) {
            String _bemfaToken = request->getParam("token", true)->value();  // bemfa token
            String _password = request->getParam("password", true)->value(); // bemfa token

            DEBUG_LOG("[HTTPD] bemfaToken=");
            DEBUG_LOG_LN(_bemfaToken);
            DEBUG_LOG("[HTTPD] password=");
            DEBUG_LOG_LN(_password);
            DynamicJsonDocument v(256);

            bemfaMqtt.saveBemfaToken(_bemfaToken);
            boot.savePassword(_password);

            v["code"] = 0;
            v["msg"] = "saved";
            auto response = request->beginResponseStream("application/json");
            serializeJsonPretty(v, *response);
            request->send(response);
        });

        _server.on("/api/restart", HTTP_POST, [this](AsyncWebServerRequest *request) {
            DEBUG_LOG_LN("[HTTPD] restart");
            DynamicJsonDocument v(256);
            v["code"] = 0;
            v["msg"] = "restart";
            auto response = request->beginResponseStream("application/json");
            serializeJsonPretty(v, *response);
            request->send(response);
            DEBUG_LOG_LN("[HTTPD] system restart");
            unsigned long t = millis();
            while (millis() - t < 1000) {
            }

            ESP.restart();
        });

        _server.on("/api/update", HTTP_POST, [this](AsyncWebServerRequest *request) {
            String binUrl = request->getParam("binUrl", true)->value();
            boot.update_begin(binUrl);
            DEBUG_LOG("[HTTPD] update:");
            DEBUG_LOG_LN(binUrl);
            DynamicJsonDocument v(256);
            v["code"] = 0;
            v["msg"] = "ok";

            auto response = request->beginResponseStream("application/json");
            serializeJsonPretty(v, *response);
            request->send(response);
        });

        _server.on("/post", HTTP_POST, [this](AsyncWebServerRequest *request) {
            String topic;
            String msg;
            if (request->hasParam(PARAM_TOPIC, true)) {
                topic = request->getParam(PARAM_TOPIC, true)->value();
            } else {
                topic = "No topic";
            }
            if (request->hasParam(PARAM_MSG, true)) {
                msg = request->getParam(PARAM_MSG, true)->value();
            } else {
                msg = "No message";
            }

            auto response = request->beginResponseStream("application/json");
            DynamicJsonDocument v(256);
            v["code"] = 0;
            v["topic"] = topic;
            v["msg"] = msg;
            serializeJsonPretty(v, *response);
            request->send(response);

            onMessageCallback(topic, msg);
        });

        // route - static contents

        _server.on("/config.html", HTTP_GET, [](AsyncWebServerRequest *request) {
            String path = "config.html";
            auto fpath = "/site/config.html";
            if (LittleFS.exists(fpath)) {
                request->send(LittleFS, fpath, mimetype(path), false);
            } else {
                request->send(404, "text/plain", "file Not Found");
            }
        });

        _server.on("/button.html", HTTP_GET, [](AsyncWebServerRequest *request) {
            String path = "index.html";
            auto fpath = "/site/index.html";
            if (LittleFS.exists(fpath)) {
                request->send(LittleFS, fpath, mimetype(path), false);
            } else {
                request->send(404, "text/plain", "file Not Found");
            }
        });

        _server.on("/door.html", HTTP_GET, [](AsyncWebServerRequest *request) {
            String path = "door.html";
            auto fpath = "/site/door.html";
            if (LittleFS.exists(fpath)) {
                request->send(LittleFS, fpath, mimetype(path), false);
            } else {
                request->send(404, "text/plain", "file Not Found");
            }
        });

        _server.on("/link/mdui.css", HTTP_GET, [](AsyncWebServerRequest *request) {
            String path = "mdui.css";
            auto fpath = "/site/link/mdui.css";
            if (LittleFS.exists(fpath)) {
                request->send(LittleFS, fpath, mimetype(path), false);
            } else {
                request->send(404, "text/plain", "file Not Found");
            }
        });

        _server.on("/link/vital.css", HTTP_GET, [](AsyncWebServerRequest *request) {
            String path = "vital.css";
            auto fpath = "/site/link/vital.css";
            if (LittleFS.exists(fpath)) {
                request->send(LittleFS, fpath, mimetype(path), false);
            } else {
                request->send(404, "text/plain", "file Not Found");
            }
        });
        _server.on("/link/zepto.min.js", HTTP_GET, [](AsyncWebServerRequest *request) {
            String path = "zepto.min.js";
            auto fpath = "/site/link/zepto.min.js";
            if (LittleFS.exists(fpath)) {
                request->send(LittleFS, fpath, mimetype(path), false);
            } else {
                request->send(404, "text/plain", "file Not Found");
            }
        });
        _server.on("/index.html", HTTP_GET, [](AsyncWebServerRequest *request) {
            String path = "index.html";
            auto fpath = "/site/index.html";
            if (LittleFS.exists(fpath)) {
                request->send(LittleFS, fpath, mimetype(path), false);
            } else {
                request->send(404, "text/plain", "file Not Found");
            }
        });
        /*
        // route - static contents
        _server.on("^\\/(.*)$", HTTP_GET, [](AsyncWebServerRequest *request) {
            auto path = request->pathArg(0);
            if (path == "") {
                path = "index.html";
            }

            DEBUG_LOG("[HTTP] static: "); DEBUG_LOG_LN(path);
            auto fpath = "/site/" + path;
            if (LittleFS.exists(fpath)) {
                request->send(LittleFS, fpath, mimetype(path), false);
            } else {
                request->send(404, "text/plain", "Not Found");
            }
        });
        */
        // route - not found
        _server.onNotFound([](AsyncWebServerRequest *request) {
            request->send(404, "text/plain", "Not Found");
        });

        _server.begin();
    };

private:
    AsyncWebServer _server;
    MessageListener _listener;

    void onMessageCallback(const String &topic, const String &msg) {
        _listener(topic, msg);
    };

    void _apiStatusGet(AsyncWebServerRequest *request) {
        auto response = request->beginResponseStream("application/json");

        DynamicJsonDocument v(384);

        v["wifi"]["ssid"] = WiFi.SSID();
        v["wifi"]["isConnected"] = WiFi.isConnected();
        v["wifi"]["hostname"] = hostname;
        v["wifi"]["localIp"] = WiFi.localIP().toString();
        v["bemfa"]["token"] = bemfaMqtt.getBemfaToken();
        v["password"] = boot.getPassword();
        v["mdns"]["isRunning"] = MDNS.isRunning();
        v["bemfa"]["topic"][0] = "";
#ifdef DEVICE_PC_WATCHMEN_PCIE
        v["bemfa"]["topic"][0] = hostname + "XXPCWatchmenPCIEXX999";
#endif
        uint32_t free;
        uint16_t maxFreeBlockSize;
        uint8_t fragmentation;
        ESP.getHeapStats(&free, &maxFreeBlockSize, &fragmentation);

        v["heap"]["free"] = free;
        v["heap"]["maxFreeBlockSize"] = maxFreeBlockSize;
        v["heap"]["fragmentation"] = fragmentation;

        serializeJsonPretty(v, *response);
        request->send(response);
    }
};
